概要
生成AIを組み込んだ業務アプリケーションでは、応答性の高いUIと、AI/MLライブラリを扱いやすいバックエンドの両立が求められます。DigitalBaseでは、この要件を満たす構成として フロントエンドにNext.js、バックエンドにFastAPI を採用するケースが多くあります。
この構成を前提に、業務AIアプリケーションの本番運用に関わるデータアクセスの集約、DB接続管理、APIのアクセス制御、認証、パフォーマンス最適化の設計指針を整理します。
システムアーキテクチャ
基本的な構成は以下のとおりです。
[ユーザー] <--> [Next.js Frontend] | | REST API / WebSocket | v [FastAPI Backend] | +-- [SQLAlchemy ORM] | | | v | [PostgreSQL] | +-- [AI Services] | +-- LLM API(Claude / OpenAI など) +-- LangChain +-- ベクトルDB(pgvector / Pinecone / Weaviate)
フロントエンドはAPI経由でのみデータにアクセスし、DBへの直接アクセスはバックエンドに集約する構成を基本とします。
データベース設計の指針
Prisma と SQLAlchemy の使い分け
DigitalBaseで推奨する基本構成は次のとおりです。
- データアクセスはバックエンド(SQLAlchemy)に集約:Next.jsから直接DBへアクセスしない
- スキーマ・マイグレーション管理:Alembic(SQLAlchemy用)で一元管理
そのうえで、以下のようなケースではPrismaの併用も選択肢になります。
Prismaを併用するメリット
- TypeScriptファーストで型安全性が高い
- Prisma StudioでデータをGUIから確認・管理できる
- Server ActionsやRoute Handlerで軽量なDBアクセスを行う用途に適している
SQLAlchemyを主軸にするメリット
- PythonのAI/MLライブラリとの連携が容易
- 複雑なクエリやトランザクション制御に強い
- FastAPIとの統合が成熟している
データアクセス層を二重化するとスキーマの整合性管理が煩雑になるため、原則は SQLAlchemyに一本化 し、Prismaは限定的な用途に留めることを推奨します。
データベース接続管理
非同期エンジンを用いる場合の接続管理の例です。
# backend/database.py import os from sqlalchemy.ext.asyncio import ( create_async_engine, AsyncSession, async_sessionmaker, ) from sqlalchemy.orm import declarative_base DATABASE_URL = os.getenv("DATABASE_URL") engine = create_async_engine( DATABASE_URL, echo=False, # 開発環境ではTrue、本番ではFalse pool_size=20, max_overflow=0, pool_pre_ping=True, # 接続の有効性を確認 ) AsyncSessionLocal = async_sessionmaker( engine, class_=AsyncSession, expire_on_commit=False ) Base = declarative_base() # 依存性注入用 async def get_db() -> AsyncSession: async with AsyncSessionLocal() as session: yield session
SQLAlchemy 2.0系では、非同期セッションのファクトリに
async_sessionmakerの使用が推奨されます。async withを抜ける際にセッションは自動的にクローズされます。
FastAPIでのAPI設計
Pydanticによるデータ検証と、ユーザーIDによるアクセス制御を組み合わせます。
Pydantic v2では、ORMオブジェクトからの変換に model_config = ConfigDict(from_attributes=True) を用います。
ユーザーIDによる絞り込みを各クエリに含めることで、他ユーザーのリソースへのアクセスを防ぎます。
AI機能の統合
LLMの呼び出しはバックエンドに集約します。
機密データを社外のAPIへ送信できない環境では、ローカルLLM(Ollama / vLLMなど)をOpenAI互換エンドポイントとして構築します。プロバイダを抽象化した設計により、後からの切り替えが容易になります。
認証・認可の実装
JWTを用いたトークン認証では、署名鍵とトークンの有効期限を管理します。
SECRET_KEY は十分なエントロピーを持つ値を環境変数で管理し、コードにハードコードしないことが前提です。アクセストークンの有効期限は短めに設定し、安全性の観点から、長期セッションにはリフレッシュトークンの使用を推奨します。
パフォーマンス最適化
1. データベース最適化
- インデックスの作成:頻繁に検索されるカラムにインデックスを付与する
- コネクションプール:
pool_sizeをワークロードに合わせて設定する - N+1問題の解消:
joinedload()やselectinload()でEager Loadingを行う
2. キャッシュ戦略
# Redisキャッシュの利用例 import json from redis import asyncio as aioredis redis = aioredis.from_url("redis://localhost") async def get_cached_conversation(conversation_id: str): cached = await redis.get(f"conversation:{conversation_id}") if cached: return json.loads(cached) return None async def set_cached_conversation(conversation_id: str, data: dict): await redis.setex( f"conversation:{conversation_id}", 300, # 5分間キャッシュ json.dumps(data), )
3. 非同期処理の活用
- 入出力待ちが発生する処理は
async defで実装する - 長時間かかる処理はBackgroundTasksやCelery、あるいはメッセージキューで非同期に実行する
要点
Next.js + FastAPI でAIアプリケーションを構築する際の設計指針は、次の6点です。
- データアクセスはバックエンド(SQLAlchemy)に集約し、フロントはAPI経由でアクセスする
- Pydantic(v2)でデータ検証と型安全性を確保する
- 入出力待ちが発生する処理に非同期処理を活用する
- JWTによる認証と、ユーザーIDによるリソースへのアクセス制御を実装する
- インデックス・コネクションプール・Redisキャッシュでパフォーマンスを最適化する
- LLMプロバイダを抽象化し、外部APIとローカルLLMを切り替えられる設計にする
自社の要件やデータの機密性に合わせて、これらの設計を調整します。
